#pragma once

#include "oj_model_mysql.hpp"
#include "../commen/util.hpp"
#include "../commen/log.hpp"
#include "../commen/httplib.h"
#include "oj_login.hpp"
#include <vector>
#include <mutex>
#include <fstream>
#include <string>
#include <queue>
#include <nlohmann/json.hpp>
#include <functional>
#include <algorithm>
#include "question.hpp"
#include "mysql_connection.hpp"

namespace ns_oj_control {
    using namespace ns_util;
    using namespace ns_log;
    using namespace ns_oj_model;
    using namespace ns_login;
    using json = nlohmann::json;

    class Machine {
    public:
        Machine(std::string ip, uint16_t port, std::mutex* mutex = nullptr)
            : ip_(ip), port_(port), load_(0), mutex_(mutex) {}

        ~Machine() {
            if (mutex_)
                delete mutex_;
        }

        bool AddLoad() {
            if (mutex_) {
                std::lock_guard<std::mutex> lock(*mutex_);
                load_++;
                return true;
            }
            return false;
        }

        bool DelLoad() {
            if (mutex_) {
                std::lock_guard<std::mutex> lock(*mutex_);
                if (load_ > 0) {
                    load_--;
                    return true;
                }
                LogMessage(Warning, "子服务器: %s:%d 负载已经为零\n", ip_.c_str(), port_);
                return false;
            }
            return false;
        }

        bool ResetLoad() {
            if (mutex_) {
                std::lock_guard<std::mutex> lock(*mutex_);
                load_ = 0;
                return true;
            }
            return false;
        }

        int load() const { return mutex_ ? load_ : -1; }
        const std::string& ip() const { return ip_; }
        uint16_t port() const { return port_; }

    private:
        std::string ip_;
        uint16_t port_;
        int load_;
        std::mutex* mutex_;
    };

    const std::string machineConfPath = "./conf/machine.conf";

    class LoadBlance {
    public:
        LoadBlance() {
            LoadConf(machineConfPath);
            LogMessage(Debug, "加载配置文件%s成功\n", machineConfPath.c_str());
        }

        bool LoadConf(const std::string& confPath) {
            std::ifstream in(confPath);
            if (!in.is_open()) {
                LogMessage(Warning, "打开配置文件失败, path: %s\n", confPath.c_str());
                return false;
            }

            std::string machineLine;
            int line = 1;
            int pos = 0;
            while (std::getline(in, machineLine)) {
                std::vector<std::string> tokens;
                StringUtil::SplitString(machineLine, tokens, ":");
                if (tokens.size() != 2) {
                    LogMessage(Warning, "配置文件出错，path：%s，line：%d\n", confPath.c_str(), line);
                    continue;
                }
                std::string ip = tokens[0];
                uint16_t port = std::stoi(tokens[1]);
                std::mutex* mtx = new std::mutex();
                machines_.emplace_back(ip, port, mtx);
                offline_.push_back(pos++);
                line++;
            }
            in.close();
            return true;
        }

        bool SmartChoice(int* id_ptr, Machine** m) {
            std::lock_guard<std::mutex> lock(mtx);
            LogMessage(Debug, "开始负载均衡选择主机\n");
            ShowMachines();

            if (online_.empty()) {
                LogMessage(Fatal, "所有的后端编译主机已经离线, 请运维的同事尽快查看\n");
                return false;
            }

            int minId = online_[0];
            int minLoad = machines_[minId].load();
            for (int id : online_) {
                LogMessage(Debug, "主机%d号（%s:%d）在线，当前的负载为%d\n",
                    id, machines_[id].ip().c_str(), machines_[id].port(), machines_[id].load());
                if (machines_[id].load() < minLoad) {
                    minId = id;
                    minLoad = machines_[id].load();
                }
            }

            *id_ptr = minId;
            *m = &machines_[minId];
            LogMessage(Debug, "负载均衡选择主机成功，选择的主机为%d号（%s:%d），当前负载为%d\n",
                minId, machines_[minId].ip().c_str(), machines_[minId].port(), machines_[minId].load());
            return true;
        }

        bool OfflineMachine(int offlineId) {
            std::lock_guard<std::mutex> lock(mtx);
            LogMessage(Debug, "开始下线主机%d号\n", offlineId);
            auto it = std::find(online_.begin(), online_.end(), offlineId);
            if (it != online_.end()) {
                machines_[offlineId].ResetLoad();
                online_.erase(it);
                offline_.push_back(offlineId);
                LogMessage(Debug, "成功下线主机%d号\n", offlineId);
                return true;
            }
            LogMessage(Warning, "指定%d主机未在线, 请检查\n", offlineId);
            return false;
        }

        bool OnlineMachine(int onlineId) {
            std::lock_guard<std::mutex> lock(mtx);
            LogMessage(Debug, "开始上线%d主机\n", onlineId);
            auto it = std::find(offline_.begin(), offline_.end(), onlineId);
            if (it != offline_.end()) {
                machines_[onlineId].ResetLoad();
                offline_.erase(it);
                online_.push_back(onlineId);
                LogMessage(Debug, "成功上线%d主机\n", onlineId);
                return true;
            }
            LogMessage(Warning, "指定%d主机未下线, 请检查\n", onlineId);
            return false;
        }

        void OnlineAllMachines() {
            std::lock_guard<std::mutex> lock(mtx);
            LogMessage(Debug, "开始上线所有主机\n");
            while (!offline_.empty()) {
                int back = offline_.back();
                offline_.pop_back();
                online_.push_back(back);
            }
            LogMessage(Debug, "成功上线所有主机\n");
            ShowMachines();
        }

        void ShowMachines() {
            std::string online_str = "当前在线主机列表: ";
            std::string offline_str = "当前离线主机列表: ";
            for (auto& id : online_) {
                online_str += std::to_string(id) + "号(" + machines_[id].ip() + ":" +
                    std::to_string(machines_[id].port()) + ") " + ", load: " +
                    std::to_string(machines_[id].load()) + "\n";
            }
            for (auto& id : offline_) {
                offline_str += std::to_string(id) + "号(" + machines_[id].ip() + ":" +
                    std::to_string(machines_[id].port()) + ") " + ", load: " +
                    std::to_string(machines_[id].load()) + "\n";
            }
            LogMessage(Info, "%s\n%s\n", online_str.c_str(), offline_str.c_str());
        }

    private:
        std::vector<Machine> machines_;
        std::vector<int> online_;
        std::vector<int> offline_;
        std::mutex mtx;
    };

    class OjControl {
    private:
        static OjControl* instance_;
        static std::mutex mutex_;
        static const int MAX_RETRY = 5;

        OjModel* model_;
        LoadBlance load_blance_;
        LoginRegisterAuthenticator login_;
        MySQLConnection* mysql_conn_;
        MYSQL* mysql_;

        OjControl() : mysql_conn_(nullptr) {
            model_ = OjModel::GetInstance();
            RecoveryMachines();
            mysql_conn_ = MySQLConnection::GetInstance();
            mysql_ = mysql_conn_->GetConnection();
        }

    public:
        static OjControl* GetInstance() {
            if (instance_ == nullptr) {
                std::lock_guard<std::mutex> lock(mutex_);
                if (instance_ == nullptr) {
                    instance_ = new OjControl();
                }
            }
            return instance_;
        }

        ~OjControl() {
            if (mysql_) {
                mysql_close(mysql_);
            }
        }

        void RecoveryMachines() {
            load_blance_.OnlineAllMachines();
        }

        bool AllQuestions(std::string* question_list) {
            if (!question_list) return false;
            return model_->GetAllQuestions(question_list);
        }

        bool Question(const std::string& number, std::string* question_info) {
            if (!question_info) return false;
            struct Question q;
            if (!model_->GetOneQuestion(number, &q)) {
                return false;
            }

            json j;
            if (q.ToJson(j)) {
                *question_info = j.dump();
                return true;
            }
            return false;
        }

        void Judge(const std::string& number, const std::string& in_json,
            const std::string& submit_time, std::string* out_json) {
            LogMessage(Debug, "开始判题, number: %s\n", number.c_str());
            LogMessage(Debug, "in_json: %s\n", in_json.c_str());

            struct Question q;
            if (!model_->GetOneQuestion(number, &q)) {
                LogMessage(Warning, "获取指定题目: %s 失败\n", number.c_str());
                return;
            }

            json in_value = json::parse(in_json);
            std::string code = in_value["code"].get<std::string>();

            json compile_value;
            compile_value["input"] = in_value.contains("input") ? in_value["input"].get<std::string>() : "";
            compile_value["cpu_limit"] = q.cpu_limit;
            compile_value["mem_limit"] = q.mem_limit;
            compile_value["code"] = code + "\n" + q.tail;

            std::string compile_json = compile_value.dump();
            LogMessage(Debug, "序列化成功, compile_json: %s\n", compile_json.c_str());

            for (int i = 0; i < MAX_RETRY; i++) {
                int id = 0;
                Machine* m = nullptr;
                if (!load_blance_.SmartChoice(&id, &m)) {
                    LogMessage(Error, "负载均衡选择主机失败\n");
                    break;
                }

                httplib::Client cli(m->ip(), m->port());
                m->AddLoad();

                if (auto res = cli.Post("/compile_and_run", compile_json, "application/json;charset=utf-8")) {
                    if (res->status == 200) {
                        *out_json = res->body;
                        m->DelLoad();

                        if (in_value.contains("id")) {
                            json result_json = json::parse(res->body);
                            int status = result_json["status"].get<int>();
                            std::string reason = result_json["reason"].get<std::string>();
                            int64_t id = std::stoll(in_value["id"].get<std::string>());

                            AddSubmitRecord(id, std::stoi(number), code,
                                std::to_string(status), reason, submit_time);
                        }

                        LogMessage(Debug, "成功调用主机%d号: %s:%d 进行判题\n",
                            id, m->ip().c_str(), m->port());
                        break;
                    }

                    m->DelLoad();
                    LogMessage(Warning, "调用主机%d号: %s:%d 判题失败, 重试第%d次, 状态码: %d\n",
                        id, m->ip().c_str(), m->port(), i + 1, res->status);
                }
                else {
                    m->DelLoad();
                    LogMessage(Warning, "调用主机%d号: %s:%d 判题失败, 可能主机已离线\n",
                        id, m->ip().c_str(), m->port());
                    load_blance_.OfflineMachine(id);
                    load_blance_.ShowMachines();
                }
            }
        }

        bool Login(const std::string& username, const std::string& password,
            const std::string& sessionId) {
            return login_.login(username, password, sessionId);
        }

        bool Register(const std::string& username, const std::string& password) {
            return login_.Register(username, password);
        }

        bool AddQuestion(const std::string& in_json) {
            return model_->AddQuestion(in_json);
        }

        bool DeleteQuestion(const std::string& number) {
            return model_->DeleteQuestion(number);
        }

        bool AddSubmitRecord(int64_t uid, int number, const std::string& code,
            const std::string& status, const std::string& reason,
            const std::string& submit_time) {
            return model_->AddSubmitRecord(uid, number, code, status, reason, submit_time);
        }

        bool GetSubmitRecords(int64_t uid, int number, std::string* out_json) {
            if (!out_json) return false;
            std::vector<SubmitRecord> records;
            if (!model_->GetSubmitRecords(uid, number, &records)) {
                return false;
            }

            json json_array = json::array();
            for (const auto& record : records) {
                json record_json;
                if (record.ToJson(record_json)) {
                    json_array.push_back(record_json);
                }
            }
            *out_json = json_array.dump();
            return true;
        }

        bool CreateQuestionList(int64_t uid, const std::string& title,
            const std::string& description, bool is_public,
            const std::string& created_at, const std::string& updated_at,
            std::string* out_json) {
            return model_->CreateQuestionList(uid, title, description, is_public,
                created_at, updated_at, out_json);
        }

        bool AddToQuestionList(int64_t uid, int64_t list_id, int number,
            const std::string& updated_at) {
            return model_->AddToQuestionList(uid, list_id, number, updated_at);
        }

        bool GetQuestionList(int64_t uid, std::string* out_json) {
            return model_->GetQuestionList(uid, out_json);
        }

        bool RemoveFromQuestionList(int64_t uid, int64_t list_id, int number) {
            return model_->RemoveFromQuestionList(uid, list_id, number);
        }

        MYSQL* GetDBConn() {
            if (!mysql_) {
                mysql_ = mysql_init(nullptr);
                if (!mysql_) {
                    LogMessage(Fatal, "MySQL初始化失败\n");
                    return nullptr;
                }
            }
            return mysql_;
        }
    };

    // 静态成员初始化
    OjControl* OjControl::instance_ = nullptr;
    std::mutex OjControl::mutex_;
}